package entity.model;

import entity.model.members.Attribute;
import entity.model.relationships.Relationship;
import misc.Base;
import misc.Title;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.stream.Collectors;

public class Entity extends Base{
    private final Class<?> aClass;
    private final entity.model.annotations.Entity annotation;
    private List<Entity> branch;

    public boolean isAbstract() {
        return isAbstract;
    }

    private final boolean isAbstract;
    protected final String tablename;

    private final List<Member> members = new ArrayList<>();
    private final List<Title> commands = new ArrayList<>();
    private final List<Relationship<?>> relationships = new ArrayList<>();
    private final Entity base;
    private int attributes;

    private final Observable observable = new Observable(){
        @Override
        public void notifyObservers(Object arg) {
            setChanged();
            super.notifyObservers(arg);
        }
    };

    public Entity(Class<?> aClass) {
        super(Title.fromClass(aClass));

        if (aClass.getSuperclass() == Object.class)
            base = null;
        else if (EntityModel.INSTANCE.ENTITY_MAP.containsKey(aClass.getSuperclass()))
            base = EntityModel.INSTANCE.ENTITY_MAP.get(aClass.getSuperclass());
        else
            base = new Entity(aClass.getSuperclass());

        this.aClass = aClass;

        annotation = aClass.getAnnotation(entity.model.annotations.Entity.class);
        isAbstract = Modifier.isAbstract(aClass.getModifiers());

        boolean createTableAttribute = true;
        if (annotation != null && annotation.tablename().length() > 0)
            tablename = annotation.tablename();
        else{
            Title temp = Title.fromClass(aClass);

            if (annotation == null || annotation.pluralize()) {
                temp = temp.pluralize();
                createTableAttribute = false;
            }

            tablename = temp.toLowerUnderscore();
        }

        if (createTableAttribute)
            createAttribute("table").value(tablename);

        createAttributes(aClass);

        createCommands();

        EntityModel.INSTANCE.ENTITY_MAP.put(aClass, this);
        EntityModel.INSTANCE.trigger(aClass);
    }

    private Attribute createAttribute(String lowerCamel) {
        return createAttribute(Title.fromLowerCamel(lowerCamel));
    }

    public synchronized void addObserver(Observer o) {
        if (attributes == 0)
            o.update(observable, this);
        else
            observable.addObserver(o);
    }

    private void createCommands() {
        for(String command : new String[]{"Create", "Update", "Delete"})
            commands.add(Title.fromUpperCamel(command));

        for(Method method :aClass.getMethods())
            if (method.getDeclaringClass() == aClass)
                commands.add(Title.fromUpperCamel(method.getName()));
    }

    private void createAttributes(Class<?> aClass) {
        if(aClass.getSuperclass() != Object.class)
            createAttribute("id");

        attributes = aClass.getDeclaredFields().length;
        for (Field field : aClass.getDeclaredFields())
            createAttribute(field);
    }

    private void createAttribute(Field field) {
        members.add(new Attribute(this, field));
    }

    protected Attribute createAttribute(Title title) {
        Attribute attribute = new Attribute(this, title);

        members.add(attribute);

        return attribute;
    }

    public boolean generateView() {
        return !isAbstract && (annotation == null || annotation.generateView());
    }

    public boolean generateController() {
        return !isAbstract && (annotation == null || annotation.generateController());
    }

    public boolean generateModel() {
        return annotation == null || annotation.generateModel();
    }

    public List<Member> getMembers() {
        return members;
    }

    public List<Title> getCommands() {
        return commands;
    }

    public String getTablename() {
        return tablename;
    }

    public List<Attribute> getAttributes() {
        return members
                .stream()
                .filter(member -> member instanceof Attribute)
                .map(member -> (Attribute) member)
                .collect(Collectors.toList());
    }

    public Entity getBase() {
        return base;
    }

    public List<Relationship<?>> getRelationships() {
        return relationships;
    }

    public void decAttribute() {
        attributes--;
        if (attributes == 0)
            observable.notifyObservers(this);
    }

    public Class<?> getType() {
        return aClass;
    }

    public List<Entity> getBranch() {
        if (branch == null)
            branch = new ArrayList<Entity>() {{
                if (base != null)
                    addAll(base.getBranch());

                add(Entity.this);
            }};

        return branch;
    }

    public String getBaseEntityName(){
        return getBase() == null ? null : getBase().getName();
    }

    public static class Member extends Base{

        private final Entity entity;
        public Member(Entity entity, Title title){
            super(title);
            this.entity = entity;
        }

        public Entity getEntity() {
            return entity;
        }
    }
}
